package com.my.netty.nio.net.selector;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

@Slf4j
public class SelectorNioServer2 {

    public static void main(String[] args) throws IOException {
        //NIO的非阻塞式TCP服务端+Selector（单线程处理）+ 通过换行分隔符处理消息边界

        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        SelectionKey sscKey = ssc.register(selector, 0, null);
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        log.debug("Selector注册key:{}", sscKey);

        while (true) {
            log.debug("Selector等待事件...");
            selector.select();

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iter = selectionKeys.iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                log.debug("Selector监听到可用事件key{}", key);

                if (key.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    log.debug("获取客户端连接:{}", sc.getRemoteAddress());

                    sc.configureBlocking(false);
                    //注册selector时将ByteBuffer作为附件注册到SelectionKey
                    SelectionKey scKey = sc.register(selector, 0, ByteBuffer.allocate(8));
                    scKey.interestOps(SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    //获取SelectionKey的附件 ByteBuffer
                    ByteBuffer buf = (ByteBuffer) key.attachment();
                    try {
                        int len = channel.read(buf);
                        if (len != -1) {
                            //ByteBuffer中通过分隔符|拆分消息
                            split(buf, '|');
                            //如果经过split之后，buffer还是满的，则说明buffer需要扩容
                            if (buf.position() == buf.limit()) {
                                ByteBuffer newBuf = ByteBuffer.allocate(buf.capacity() * 2);
                                //将buf内容拷贝到newBuf中
                                buf.flip();
                                newBuf.put(buf);
                                //将newBuf替换原有的附件
                                key.attach(newBuf);
                                log.debug("{} ByteBuffer完成扩容 {} -> {}", channel.getRemoteAddress(), buf.capacity(), newBuf.capacity());
                            }
                        } else {
                            log.info("客户端断开连接 {}", channel.getRemoteAddress());
                            key.cancel();
                        }
                    } catch (IOException e) {
                        log.error("客户端强制断开连接 {}", channel.getRemoteAddress(), e);
                        key.cancel();
                    }
                }

                iter.remove();
            }
        }
    }

    /*
        这里沿用之前写的一个处理半包粘包的split函数
        注意 处理消息边界还要解决2个问题：
        1.怎么让每个channel使用各自的ByteBuffer接收未处理完的数据，保证互不影响
            -> 注册selector时将ByteBuffer作为附件（register第3个参数）注册到SelectionKey
        2.ByteBuffer空间不够装下第一条消息，该如何扩容
            -> 经过split处理后，如果ByteBuffer还是满的，则进行2倍扩容
     */
    private static void split(ByteBuffer buffer, char splitCh) {
        //切为读模式
        buffer.flip();

        for (int i = 0; i < buffer.limit(); i++) {
            byte b = buffer.get(i);
            if (b == splitCh) {
                //存储一条完整消息的ByteBuffer
                int len = i - buffer.position() + 1;
                ByteBuffer msgBuf = ByteBuffer.allocate(len);
                for (int j = 0; j < len; j++) {
                    msgBuf.put(buffer.get());
                }
                //打印消息内容
                msgBuf.flip();
                log.info("拆分出1条消息: {}", StandardCharsets.UTF_8.decode(msgBuf));
            }
        }

        //切回写模式，并删除已处理的字节，供下次接收数据
        buffer.compact();
    }
}
